/* Copyright (C) 2008, 2009, 2010 Faindu GmbH, Switzerland.
* All Rights Reserved. The following is Source Code and is subject to all restrictions 
* on such code as contained in the license accompanying this project. */
package blocstac.editimage.base
{
	
	import blocstac.editimage.EditState;
	
	import flash.display.BitmapData;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;

/**
 * ViewModel holds also the transient view state but not the permanent state which is in ImageModel.
 */
public class ViewModel
{

	private var _imageModel:ImageModel
	private var _interactionIsRunning:Boolean
	private var _initialImageCenterInImageEditCompX:Number = 0
	private var _initialImageCenterInImageEditCompY:Number = 0
	private var _snapToX:Number = 0
	private var _snapToY:Number = 0
	private var _componentWidth:Number = 0
	private var _componentHeight:Number = 0
	
	public function ViewModel(imageModel:ImageModel) {
		_imageModel = imageModel
	}
	
	//--------------------------------------------------------------------------
	//
	//  Interaction
	//
	//--------------------------------------------------------------------------
	/**
	 * Return false if an other interaction is running and no other interaction can be started.
	 */
	public function interactionStarts():Boolean {
		if (_interactionIsRunning || ! _editState.isEditable) {
			return false
		}
		var center:Point = _rotationMatrix.transformPoint(imageCenterInImageComp)
		_initialImageCenterInImageEditCompX = center.x + _snapToX
		_initialImageCenterInImageEditCompY = center.y + _snapToY
		_interactionIsRunning = true
		return true
	}
	
	public function interactionEnds():void {
		_interactionIsRunning = false
		updateRotationMatrixAtInteractionEnd()
	}

	//--------------------------------------------------------------------------
	//
	//  State values
	//
	//--------------------------------------------------------------------------		
	//----------------------------------
	//  userViewScale
	//----------------------------------
	private var _oldUserViewScale:Number = 1
	private var _userViewScale:Number = 1
	private var _userViewScaleChanged:Boolean
	public function get userViewScale():Number {
		return _userViewScale
	}
	internal function updateUserViewScale(value:Number):void {
		_oldUserViewScale = _userViewScale
		_userViewScale = value
		_userViewScaleChanged = true
	}
	
	public function userViewIsScaled():Boolean {
		return _userViewScale != 1 || (_fixScale != 0 && _fixScale != _userViewScale)
	}

	//----------------------------------
	//  Image width is larger than component width
	//----------------------------------
	private var _fixScale:Number = 1
	private var _maxComponentWidthInContainer:Number
	/**
	 * When the EditImage component is too wide, only a fraction of the image is visible.
	 */
	public function maxComponentWidthInContainer(maxComponentWidth:Number):void {
		_maxComponentWidthInContainer = maxComponentWidth
		if (internalComponentWidth != maxComponentWidth) {
			_fixScale = maxComponentWidth / imageWidth 
			updateUserViewScale(_fixScale)
			updateRotationMatrixAtInteractionEnd()
		}
	}

	public function get imageIsWiderThanComponent():Boolean {
		return _fixScale != 1
	}
	
	public function scaleToImageSize(transformImageForCrop:Matrix, cropMask:Rectangle):void {
		var inverseFixScale:Number = 1 / _fixScale
		transformImageForCrop.scale(inverseFixScale, inverseFixScale)
		cropMask.x *= inverseFixScale
		cropMask.y *= inverseFixScale
		cropMask.width *= inverseFixScale
		cropMask.height *= inverseFixScale
	}
		
	//----------------------------------
	//  pan X / Y
	//----------------------------------
	private var _panChanged:Boolean
	private var _panX:Number = 0
	private var _panY:Number = 0
	internal function applyPan(relativeMoveX:Number, relativeMoveY:Number):void {
		_panX = _panX + relativeMoveX
		_panY = _panY + relativeMoveY
		_panChanged = true
	}
	
	//----------------------------------
	//  switchToEditState
	//----------------------------------
	private var _editState:EditState = EditState.zoomable
	internal function switchToEditState(editState:EditState):void {
		_editState = editState
	}
	
	//--------------------------------------------------------------------------
	//
	//  Calculated view values
	//
	//--------------------------------------------------------------------------		
	//----------------------------------
	//  Image data
	//----------------------------------	
	/** @inheritDoc */
	public function get imageData():BitmapData {
		return _imageModel.imageData // due to performance, imageData is not cloned
	}

	public function get imageDataChanged():Boolean {
		return _imageModel.imageDataChanged
	}
	
	public function resetImageDataChanged():void {
		if (_imageModel.imageDataChanged) {
			_panX = 0
			_panY = 0
			_userViewScale = 1
		}
		_imageModel.resetImageDataChanged()
	}

	//----------------------------------
	//  Rotation
	//----------------------------------	
	public function get angle():Number {
		return _imageModel.angle
	}

	private var _rotationMatrix:Matrix = new Matrix()	
	public function get rotationMatrix():Matrix {
		if (_imageModel.angleHasChanged) {
			_imageModel.angleHasChanged = false
			
			var newRotationMatrix:Matrix = new Matrix()
			newRotationMatrix.translate(-_initialImageCenterInImageEditCompX, -_initialImageCenterInImageEditCompY)
			newRotationMatrix.rotate(_imageModel.angle)
			newRotationMatrix.translate(_initialImageCenterInImageEditCompX, _initialImageCenterInImageEditCompY)
			newRotationMatrix.translate(-_snapToX, -_snapToY)
			_rotationMatrix = newRotationMatrix
		}
		return _rotationMatrix
	}
	
	private function updateRotationMatrixAtInteractionEnd():void {
		_imageModel.angleHasChanged = false
		_rotationMatrix = new Matrix()
		
		if (_imageModel.angle != 0) {
	 		_rotationMatrix.translate(-halfImageViewWidth, -halfImageViewHeight)
			_rotationMatrix.rotate(_imageModel.angle)
			_rotationMatrix.translate(halfImageViewWidth, halfImageViewHeight) 
		}
		
		var lt:Point = _rotationMatrix.transformPoint(new Point(0, 0))
		var lb:Point = _rotationMatrix.transformPoint(new Point(0, imageViewHeightScaled))
		var rb:Point = _rotationMatrix.transformPoint(new Point(imageViewWidthScaled, imageViewHeightScaled))
		var rt:Point = _rotationMatrix.transformPoint(new Point(imageViewWidthScaled, 0))
		
		_snapToX = Math.min(lt.x, lb.x, rb.x, rt.x)
		_snapToY = Math.min(lt.y, lb.y, rb.y, rt.y)
		_componentWidth = Math.max(lt.x, lb.x, rb.x, rt.x) - _snapToX
		_componentHeight = (Math.max(lt.y, lb.y, rb.y, rt.y) - _snapToY)
		_rotationMatrix.translate(-_snapToX, -_snapToY)
	}
	
	//----------------------------------
	//  Image width/height
	//----------------------------------	
	private function get imageWidthUnscaled():Number {
		var imageData:BitmapData = _imageModel.imageData
		return imageData ? imageData.width : 0
	}
	private function get imageHeightUnscaled():Number {
		var imageData:BitmapData = _imageModel.imageData
		return imageData ? imageData.height : 0
	}
	
	
	private function get imageViewWidth():Number {
		return imageWidthUnscaled * _fixScale
	}
	private function get imageViewHeight():Number {
		return imageHeightUnscaled * _fixScale
	}
	
	
	public function get imageViewWidthScaled():Number {
		return imageViewWidth * _imageModel.editedScaleX
	}
	public function get imageViewHeightScaled():Number {
		return imageViewHeight * _imageModel.editedScaleY
	}


	public function get halfImageViewWidth():Number {
		return imageViewWidthScaled/2
	}
	private function get halfImageViewHeight():Number {
		return imageViewHeightScaled/2
	}
	

	public function get imageWidth():Number {
		return imageWidthUnscaled * _imageModel.editedScaleX
	}
	public function get imageHeight():Number {
		return imageHeightUnscaled * _imageModel.editedScaleY
	}

	//----------------------------------
	//  Points
	//----------------------------------	
	/** @inheritDoc */
	public function get imageCenterInImageComp():Point {
		return new Point(halfImageViewWidth, halfImageViewHeight)
	}
	
	/** @inheritDoc */
	public function get brImageCornerInRotationCompUnscaled():Point {
		return new Point(imageWidthUnscaled, imageHeightUnscaled)
	}

	//----------------------------------
	//  Scale
	//----------------------------------	
	public function get editedScaleX():Number {
		return _imageModel.editedScaleX
	}
	public function get editedScaleY():Number {
		return _imageModel.editedScaleY
	}
	
	/** @inheritDoc */
	public function get imageMatrix():Matrix {
		var newMatrix:Matrix = new Matrix()
		if (_editState.isZoomable) {
			newMatrix.scale(_imageModel.editedScaleX*_userViewScale, _imageModel.editedScaleY*_userViewScale)
			if (_userViewScaleChanged) {
				scaleInVisualCenter()
			}
			if (_panChanged || _userViewScaleChanged) {
				_panChanged = false
				_userViewScaleChanged = false
				validateUserPan()
			}
			newMatrix.translate(_panX, _panY)
		} else {
			newMatrix.scale(_imageModel.editedScaleX*_fixScale, _imageModel.editedScaleY*_fixScale)
		}
		return newMatrix
	}
	
	private function validateUserPan():void {
		if (0 < _panX) {
			_panX = 0
		} else {
			var fullImageWidth:Number = imageWidth * _userViewScale
			if (-_panX + imageViewWidthScaled > fullImageWidth) {
				_panX = imageViewWidthScaled - fullImageWidth
			}
		}
		
		if (0 < _panY) {
			_panY = 0
		} else {
			var fullImageHeight:Number = imageHeight * _userViewScale
			if (-_panY + imageViewHeightScaled > fullImageHeight) {
				_panY = imageViewHeightScaled - fullImageHeight
			}
		}
	}
	
	private function scaleInVisualCenter():void {
		var scaleRatio:Number = _userViewScale/_oldUserViewScale
		var halfImageWidth:Number = this.halfImageViewWidth
		var halfImageHeight:Number = this.halfImageViewHeight
		_panX = halfImageWidth - (scaleRatio * (halfImageWidth - _panX))
		_panY = halfImageHeight - (scaleRatio * (halfImageHeight - _panY))
	}
	
	//----------------------------------
	//  Component size
	//----------------------------------
	public function get internalComponentWidth():Number {
		return imageWidthUnscaled * _imageModel.editedScaleX
	}
	
	/** @inheritDoc */
	public function get componentWidth():Number {
		if (_componentWidth == 0) {
			_componentWidth = imageViewWidthScaled * _userViewScale
		}
		return _componentWidth
	}

	/** @inheritDoc */
	public function get componentHeight():Number {
		if (_componentHeight == 0) {
			_componentHeight = imageViewHeightScaled * _userViewScale
		}
		return _componentHeight
	}
	
}
}